#!/usr/bin/perl

# $Id: hosts_checker.pl 173 2010-07-04 13:38:40Z dolbanavt $
# $Date: 2010-07-04 16:38:45 +0300 (Вск, 04 Июл 2010) $
# $HeadURL: https://robosearch.googlecode.com/svn/branches/reborn/hosts_checker.pl $
# $LastChangedRevision$

use lib::abs qw( base_lib lib );
use JIP::ToolSet;
use Carp qw( croak );
use Const::Fast qw( const );
use English qw( -no_match_vars );
use Pod::Usage qw( pod2usage );
use Getopt::Long 2.24 qw( :config no_auto_abbrev no_ignore_case );
use File::Spec::Functions qw( catfile );
use FindBin qw( $RealBin );
use Configurator;
use Log;
use Proc::Lock;
use SQLWrapper;
use DBWrapperFactory;
use JIP::Utils qw( get_log_name );
use HostChecker::Utils qw(
    find_active_hosts_in
    resolve_nbn_for
    find_active_ftp_servers
);

our $VERSION = '1.0';

# more meaningfull names for pod2usage's -verbose parameter
const my $SHOW_USAGE_ONLY         => 0;
const my $SHOW_BRIEF_HELP_MESSAGE => 1;
const my $SHOW_FULL_MANUAL        => 2;

# more meaningfull names for pod2usage's -exitval parameter
const my $RETURN_SUCCESS     => 0;
const my $RETURN_FATAL_ERROR => 1;
const my $RETURN_BAD_OPTIONS => 2;

#-- ui -------------------------------------------------------------------------
my $options_okay = GetOptions(
    'help'   => \my $is_help_requested,
    'conf=s' => \my $path_to_config_file,
    'mode=s' => \my $mode,
);

# show appropriate help message
pod2usage( -exitval => $RETURN_BAD_OPTIONS, -verbose => $SHOW_USAGE_ONLY )
    unless $options_okay;

pod2usage( -exitval => $RETURN_SUCCESS, -verbose => $SHOW_FULL_MANUAL )
    if $is_help_requested;

# check required options
pod2usage(
    -exitval => $RETURN_BAD_OPTIONS,
    -message => 'required parameter "conf" not defined',
    -verbose => $SHOW_USAGE_ONLY,
) unless $path_to_config_file;

pod2usage(
    -exitval => $RETURN_BAD_OPTIONS,
    -message => 'required parameter "mode" not defined',
    -verbose => $SHOW_USAGE_ONLY,
) unless $mode;

#-- init settings --------------------------------------------------------------
my $conf = Configurator->new( conf => $path_to_config_file )->get_settings;

#-- init log -------------------------------------------------------------------
our $log = Log->new(
    log_file  => catfile( $conf->hosts_checker->log_dir, get_log_name() ),
    log_level => $conf->hosts_checker->log_level,
);

#-- signal handlers ------------------------------------------------------------
require( catfile( $RealBin, 'signal_handlers.pl' ) );

#-- setup lock -----------------------------------------------------------------
$log->Note('setup lock');

my $process = Proc::Lock->new( lock_file => $conf->hosts_checker->lock_file );

$process->lock();

#-- db wrapper -----------------------------------------------------------------
my $db_wrapper = DBWrapperFactory->create_hosts_checker_wrapper(
    dbh => SQLWrapper->new(
        dsn  => $conf->db->dsn,
        user => $conf->db->user,
        pass => $conf->db->pass,
    )->get_db_handle,
);

#-- main -----------------------------------------------------------------------
my @known_hosts = $db_wrapper->get_known_hosts();

$log->Note( 'number of known hosts: '. scalar @known_hosts );

if ( $mode eq 'find' ) {
    $log->Note('"find" mode');

    for my $each_subnet_mask ( @{ $conf->hosts_checker->networks } ) {
        my @new_hosts;

        for my $each_ip_address ( find_active_hosts_in( $each_subnet_mask ) ) {
            unless ( $each_ip_address ~~ @known_hosts ) {
                push @known_hosts, $each_ip_address;
                push @new_hosts,   $each_ip_address;

                $db_wrapper->add_new_host( $each_ip_address );
            }
        }

        if ( @new_hosts ) {
            $log->Note( 'new hosts in network '. $each_subnet_mask .':' );
            $log->Note( $ARG ) foreach @new_hosts;
        }
        else {
            $log->Note( 'network '. $each_subnet_mask .' without new hosts' );
        }
    }
}
elsif ( $mode eq 'refresh' ) {
    $log->Note('"refresh" mode');

    my @servers_online = find_active_ftp_servers(
        max_threads => $conf->hosts_checker->max_threads,
        hosts_ref   => \@known_hosts,
    );

    $log->Note('servers online:') if @servers_online;

    for my $each_ip_address ( @servers_online ) {
        my $netbios_name = resolve_nbn_for( $each_ip_address ) // '-';

        $log->Note( $each_ip_address .' with nbn '. $netbios_name );

        $db_wrapper->update_status_for( $each_ip_address );

        $db_wrapper->update_netbios_name(
            ip_address   => $each_ip_address,
            netbios_name => $netbios_name,
        );
    }

    $log->Note( 'number of active servers: '. @servers_online );
}
else {
    croak 'unexpected mode: '. $mode;
}

#-- finalyze -------------------------------------------------------------------
$log->Note('-- finalyze -----------------------------------------------------');
$log->Note('unset lock...') and $process->unlock() and $log->Note('exiting');

#-- end ------------------------------------------------------------------------

__END__

=head1 NAME

hosts_checker.pl - утилита для поиска хостов в сетях

=head1 SYNOPSIS

hosts_checker.pl [options]

 Options:
   --help            show full help message
   --conf            путь к конфигурационному файлу
   --mode            режим работы (find или refresh)

=head1 USAGE

/path/to/hosts_checker.pl --help

/path/to/hosts_checker.pl --conf=./robo_conf.pm --mode=refresh

=head1 DESCRIPTION

Находит в сети хосты которых нет в базе данных (режим find) или
проверяет статус (online or offline) уже найденных (режим refresh).
Результат записывает в лог и сохраняет в базу данных.

=head1 DEPENDENCIES

lib::abs (from CPAN)

Const::Fast (from CPAN)

JIP::ToolSet (from base_lib)

Configurator (from base_lib)

Log (from base_lib)

Proc::Lock (from base_lib)

SQLWrapper (from base_lib)

JIP::Utils (from base_lib)

=head1 EXIT STATUS

0 - программа выполнена успешно

1 - критическая ошибка программы

2 - неверные параметры командной строки

=head1 AUTHOR

Vladimir Zhavoronkov, <dolbanavt (at) Google Mail>

http://dolbanavt.livejournal.com/

=head1 LICENSE AND COPYRIGHT

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

=cut
